using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Ink;
using System.Windows.Forms;
using Microsoft.StylusInput;
using Microsoft.StylusInput.PluginData;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace Woz
{
    /// <summary>
    /// base class for tool that implements both istylusasyncplugin and istylussyncplugin
    /// </summary>
    public abstract class Tool : IStylusAsyncPlugin, IStylusSyncPlugin
    {
        /// <summary>
        /// default tool constructor
        /// </summary>
        public Tool(Control pCanvasControl)
        {
            // set the canvas control
            this.mCanvasControl = pCanvasControl;

            // initialize the graphics
            this.mGraphics = null;

            // create the list of toolstrips
            this.mToolStripNames = new List<string>();

            // create the list of valid modes
            this.mValidModes = new List<string>();
        }

        public Woz.OptionsPanel OptionsPanel
        {
            get
            {
                return this.mOptionsPanel;
            }
        }

        public List<string> ToolStripNames
        {
            get
            {
                return this.mToolStripNames;
            }
        }

        public List<string> ValidModes
        {
            get
            {
                return this.mValidModes;
            }
        }

        /// <summary>
        /// get the name of the tool
        /// </summary>
        public abstract string Name
        {
            get;
        }

        /// <summary>
        /// gets the filename of the icon associated with this tool
        /// </summary>
        public abstract string IconFileName
        {
            get;
        }

        /// <summary>
        /// reset the tool
        /// </summary>
        public abstract void Reset();

        /// <summary>
        /// updates the current cursor on the canvas control
        /// </summary>
        /// <param name="pCursor">the cursor to use for the canvas control</param>
        protected void SetCurrentCursor(System.Windows.Forms.Cursor pCursor)
        {
            sCursor = pCursor;
        }

        /// <summary>
        /// sets the cursor
        /// </summary>
        private void SetCursor(Point point)
        {
            // get the original cursor
            System.Windows.Forms.Cursor cursor = sCursor;

            // change the cursor to arrow if the point is outside the bounds of the canvas
            if (!Woz.InkSpaceConverter.Instance.InkSpaceCanvasRectangle.Contains(point))
            {
                cursor = Woz.ResourceManager.Instance.Cursors["arrow"];
            }

            // set the cursor of the canvas control to the new cursor
            if (this.mCanvasControl.Cursor != cursor)
            {
                this.mCanvasControl.Cursor = cursor;
            }
        }

        /// <summary>
        /// initialize the graphics object of the control this tool is drawing on
        /// </summary>
        protected void InitializeGraphics()
        {
            // stay here until canvas is no longer busy

            // let everyone know that we have began using the graphics 
            if (this.BeginUsingGraphics != null)
            {
                this.BeginUsingGraphics.Invoke(this, EventArgs.Empty);
            }

            // create the graphics and set it to inkspace coordinates
            this.mGraphics = Woz.InkSpaceConverter.Instance.InitializeInkGraphics(this.mCanvasControl.CreateGraphics());

            // transform the graphics object
            this.mGraphics.Transform = Woz.DynamicVariables.Instance.TransformationMatrix;

            // clip to the canvas only
            this.mGraphics.SetClip(Woz.InkSpaceConverter.Instance.InkSpaceCanvasRectangle);
        }

        /// <summary>
        /// release the graphics object of the control this tool is drawing on
        /// </summary>
        protected void ReleaseGraphics()
        {
            // dispose the graphics object
            this.mGraphics.Dispose();

            // now clear the graphics object
            this.mGraphics = null;

            // let everyone know that we have began using the graphics 
            if (this.EndUsingGraphics != null)
            {
                this.EndUsingGraphics.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// invalidates a certain rectangle of the canvas
        /// </summary>
        /// <param name="pInvalidRect">the rectangle of the canvas to invalidate</param>
        protected void InvalidateRect(Rectangle pInvalidRect)
        {
           // convert it to physical dimensions and inflate to avoid dirt
            pInvalidRect = Woz.InkSpaceConverter.Instance.UnapplyTransformationFromInk(pInvalidRect);
            pInvalidRect.Inflate(2, 2);

            // check if the last invalidated rectangle is this one
            if (this.mLastInvalidatedRectangle.Equals(pInvalidRect))
            {
                return;
            }

            // set the last invalidated rectangle
            this.mLastInvalidatedRectangle = pInvalidRect;

            // now invalidate the canvas control
            this.mCanvasControl.Invalidate(pInvalidRect);
        }

        public virtual void Draw(Graphics pGraphics)
        {
        }

        /// <summary>
        /// get what kind of events the tool intersects
        /// </summary>
        public virtual DataInterestMask DataInterest
        {
            get
            {
                return DataInterestMask.StylusDown | DataInterestMask.Packets | DataInterestMask.StylusUp | DataInterestMask.InAirPackets | DataInterestMask.StylusInRange;
            }
        }

        /// <summary>
        /// handler of stylus down event
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pData">the event data</param>
        public void StylusDown(RealTimeStylus pSender, StylusDownData pData)
        {
            // get the points
            Point[] points = this.ConvertPoints(pData);

            // make sure there really are packets
            if (points.Length > 0)
            {
                // call the internal function
                this.OnStylusDown(points[0]);

                // update the cursor
                this.SetCursor(points[0]);
            }
        }

        protected virtual void OnStylusDown(Point pLocation)
        {
        }

        /// <summary>
        /// handler of stylus movement
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pData">the event data</param>
        public void Packets(RealTimeStylus pSender, PacketsData pData)
        {
            // get the points
            Point[] points = this.ConvertPoints(pData);

            // make sure there really are packets
            if (points.Length > 0)
            {
                // call the internal function
                this.OnPackets(points);

                // update the cursor
                this.SetCursor(points[0]);
            }
        }

        protected virtual void OnPackets(Point[] pLocations)
        {
        }

        /// <summary>
        /// handler of stylus up event
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pData">the event data</param>
        public void StylusUp(RealTimeStylus pSender, StylusUpData pData)
        {
            // get the points
            Point[] points = this.ConvertPoints(pData);

            // make sure there really are packets
            if (points.Length > 0)
            {
                // call the internal function
                this.OnStylusUp(points[0]);

                // update the cursor
                this.SetCursor(points[0]);
            }
        }

        protected virtual void OnStylusUp(Point pLocation)
        {
        }

        /// <summary>
        /// handler for stylus button down event
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pData">the event data</param>
        public void StylusButtonDown(RealTimeStylus pSender, StylusButtonDownData pData)
        {
            this.OnStylusButtonDown(pData.ButtonIndex);
        }

        protected virtual void OnStylusButtonDown(int pButtonIndex)
        {
        }

        /// <summary>
        /// handler for stylus button up event
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pData">the event data</param>
        public void StylusButtonUp(RealTimeStylus pSender, StylusButtonUpData pData)
        {
            this.OnStylusButtonUp(pData.ButtonIndex);
        }

        protected virtual void OnStylusButtonUp(int pButtonIndex)
        {
        }

        /// <summary>
        /// handler for stylus in air packets
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pData">the event data</param>
        public void InAirPackets(RealTimeStylus pSender, InAirPacketsData pData)
        {
            // get the points
            Point[] points = this.ConvertPoints(pData);

            // make sure there really are packets
            if (points.Length > 0)
            {
                // call the internal function
                this.OnInAirPackets(points);

                // update the cursor
                this.SetCursor(points[0]);
            }
        }

        protected virtual void OnInAirPackets(Point[] pLocations)
        {
        }

        public void StylusInRange(RealTimeStylus pSender, StylusInRangeData pData)
        {
            this.CheckIfStylusFlipped(pData.Stylus);
        }

        public void StylusOutOfRange(RealTimeStylus pSender, StylusOutOfRangeData pData)
        {
        }

        #region Unused IStylusPlugin event handlers
        // simply define these to satisfy the interfaces were inheriting
        public void CustomStylusDataAdded(RealTimeStylus sender, CustomStylusData data) { }
        public void Error(RealTimeStylus sender, ErrorData data) { }
        public void RealTimeStylusDisabled(RealTimeStylus sender, RealTimeStylusDisabledData data) { }
        public void RealTimeStylusEnabled(RealTimeStylus sender, RealTimeStylusEnabledData data) { }
        public void SystemGesture(RealTimeStylus sender, SystemGestureData data) { }
        public void TabletAdded(RealTimeStylus sender, TabletAddedData data) { }
        public void TabletRemoved(RealTimeStylus sender, TabletRemovedData data) { }
        #endregion

        public void ProcessSelection()
        {
            this.OnProcessSelection();
            this.SetCursor(Point.Empty);
        }

        protected abstract void OnProcessSelection();

        public void ProcessDeselection()
        {
            this.OnProcessDeselection();
            this.SetCursor(Point.Empty);
        }

        protected abstract void OnProcessDeselection();

        private Point[] ConvertPoints(StylusDataBase pData)
        {
            // save all the points
            Point lastPoint = Point.Empty;
            Point currentPoint;
            List<Point> convertedPoints = new List<Point>((int)(pData.Count / (float)pData.PacketPropertyCount));
            for (int i = 0; i < pData.Count; i += pData.PacketPropertyCount)
            {
                currentPoint = new Point(pData[i], pData[i + 1]);
                if ((lastPoint.X != currentPoint.X) || (lastPoint.Y != currentPoint.Y))
                {
                    convertedPoints.Add(currentPoint);
                }
                lastPoint = currentPoint;
            }

            // transform the points to world space coordinates
            Point[] points = convertedPoints.ToArray();
            if (points.Length > 0)
            {
                Woz.DynamicVariables.Instance.InverseTransformationMatrix.TransformPoints(points);
            }

            // return the points
            return points;
        }

        /// <summary>
        /// checks to see if the stylus has been flipped
        /// </summary>
        /// <param name="pStylus">the stylus to check</param>
        private void CheckIfStylusFlipped(Stylus pStylus)
        {
            // check to see if the stylus was inverted
            if (sStylusInverted != pStylus.Inverted)
            {
                // set the stylus inverted
                sStylusInverted = pStylus.Inverted;

                // fire event
                if (Woz.Tool.StylusFlipped != null)
                {
                    Woz.Tool.StylusFlipped.Invoke(this, sStylusInverted ? Woz.StylusFlippedEventArgs.FlatSide : Woz.StylusFlippedEventArgs.PointySide);
                }
            }
        }

        /// <summary>
        /// event fired when tool begins using graphics
        /// </summary>
        public event EventHandler BeginUsingGraphics;

        /// <summary>
        /// event fired when tool ends using graphics
        /// </summary>
        public event EventHandler EndUsingGraphics;

        /// <summary>
        /// event fired when the stylus is flipped
        /// </summary>
        public static event Woz.StylusFlippedEventHandler StylusFlipped;

        /// <summary>
        /// save a reference to our parent canvas
        /// </summary>
        private Control mCanvasControl;

        /// <summary>
        /// save a reference to the graphics object
        /// </summary>
        protected Graphics mGraphics;

        /// <summary>
        /// the options panel that gets copied to the client when this tool is selected
        /// </summary>
        protected Woz.OptionsPanel mOptionsPanel;

        /// <summary>
        /// list of toolstrips this tool wants visible when it becomes selected
        /// </summary>
        protected List<string> mToolStripNames;

        /// <summary>
        /// the last invalidated rectangle
        /// </summary>
        private Rectangle mLastInvalidatedRectangle;

        /// <summary>
        /// list of modes that this tool can be used in
        /// </summary>
        protected List<string> mValidModes;

        /// <summary>
        /// save the current cursor
        /// </summary>
        private static System.Windows.Forms.Cursor sCursor;

        /// <summary>
        /// flag indicating whether the stylus is inverted or not
        /// </summary>
        private static bool sStylusInverted;
    }
}
